package com.stark.commons.spring.web.support.page;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;

/**
 * 自定义 {@link org.springframework.data.domain.Page} 的序列化和反序列化解析器。
 * <p>解决由于默认实现 {@link org.springframework.data.domain.PageImpl} 没有无参构造方法造成无法序列化的问题。
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0
 */
@JsonComponent
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
public class PageCombinedSerializer {
	
	@SuppressWarnings("rawtypes")
	public static class PageSerializer extends JsonSerializer<Page> {
		
		@Override
		public void serialize(Page page, JsonGenerator gen, SerializerProvider serializers) throws IOException {
			gen.writeStartObject();
			// 泛型类型
			gen.writeStringField("type", page.getContent().isEmpty() ? Object.class.getName() : page.getContent().get(0).getClass().getName());
			// 分页数据
			gen.writeObjectField("content", page.getContent());
			// 分页信息
			gen.writeFieldName("pageable");
			gen.writeStartObject();
			gen.writeNumberField("pageNumber", page.getPageable().getPageNumber());
			gen.writeNumberField("pageSize", page.getPageable().getPageSize());
			// 排序
			gen.writeFieldName("sort");
			gen.writeStartArray();
			page.getPageable().getSort().forEach(order -> {
				try {
					gen.writeStartObject();
					gen.writeStringField("property", order.getProperty());
					gen.writeStringField("direction", order.getDirection().name());
					gen.writeEndObject();
				} catch (IOException e) {
					throw new RuntimeException("org.springframework.data.domain.Sort.Order 序列化失败", e);
				}
			});
			gen.writeEndArray();
			gen.writeEndObject();
			// 总数
			gen.writeNumberField("totalElements", page.getTotalElements());
			// 总页数
			gen.writeNumberField("totalPages", page.getTotalPages());
			gen.writeEndObject();
		}
		
	}
	
	@SuppressWarnings("rawtypes")
	public static class PageDeserializer extends JsonDeserializer<Page> {

		@SuppressWarnings("unchecked")
		@Override
		public Page deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
			JsonNode root = p.getCodec().readTree(p);
			// 泛型类型
			String type = root.get("type").asText();
			// 分页数据
			List content = new ArrayList<>();
			if (root.get("content").size() > 0) {
				ObjectMapper objectMapper = new ObjectMapper();
				Class<?> clazz;
				try {
					clazz = Class.forName(type);
				} catch (ClassNotFoundException e) {
					throw new RuntimeException("找不到 " + type + " 类", e);
				}
				root.get("content").forEach(object -> {
					try {
						content.add(objectMapper.readValue(object.toString(), clazz));
					} catch (Exception e) {
						throw new RuntimeException(type + " 反序列化失败", e);
					}
				});
			}
			// 分页信息
			int pageNumber = root.get("pageable").get("pageNumber").asInt();
			int pageSize = root.get("pageable").get("pageSize").asInt();
			// 排序
			List<Order> orders = new ArrayList<>();
			root.get("pageable").get("sort").forEach(order -> {
				orders.addAll(Sort.by(Direction.valueOf(order.get("direction").asText()), order.get("property").asText()).toList());
			});
			Sort sort = Sort.by(orders);
			// 总数
			int total = root.get("totalElements").asInt();
			
			PageImpl page = new PageImpl<>(content, PageRequest.of(pageNumber, pageSize, sort), total);
			return page;
		}
		
	}
    
}